/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: mouse driver
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2022-06-05     JasonHu           Init
 */

#define NX_LOG_NAME "ps2mouse"
#define NX_LOG_LEVEL NX_LOG_INFO
#include <base/log.h>

#ifdef CONFIG_NX_DRIVER_PS2_MOUSE

#include <base/page.h>
#include <base/driver.h>
#include <base/malloc.h>
#include <base/debug.h>
#include <base/delay_irq.h>
#include <base/fifo.h>
#include <base/semaphore.h>
#include <base/thread.h>
#include <base/memory.h>

#include <drvfw/input_event.h>

#include <interrupt.h>
#include <io.h>

#define DRV_NAME "ps2 mouse driver"
#define DRV_VERSION "0.1"

#define DNX_EV_NAME "mouse0"

#define I8042_BUFFER 0x60
#define I8042_STATUS 0x64
#define I8042_ACK 0xFA
#define I8042_BUFFER_FULL 0x01
#define I8042_WHICH_BUFFER 0x20
#define I8042_MOUSE_BUFFER 0x20
#define I8042_KEYBOARD_BUFFER 0x00

#define PS2MOUSE_SET_RESOLUTION 0xE8
#define PS2MOUSE_STATUS_REQUEST 0xE9
#define PS2MOUSE_REQUEST_SINGLE_PACKET 0xEB
#define PS2MOUSE_GET_DEVICE_ID 0xF2
#define PS2MOUSE_SET_SAMPLE_RATE 0xF3
#define PS2MOUSE_ENABLE_PACKET_STREAMING 0xF4
#define PS2MOUSE_DISABLE_PACKET_STREAMING 0xF5
#define PS2MOUSE_SET_DEFAULTS 0xF6
#define PS2MOUSE_RESEND 0xFE
#define PS2MOUSE_RESET 0xFF

#define PS2MOUSE_LEFT_CLICK 0x01
#define PS2MOUSE_RIGHT_CLICK 0x02
#define PS2MOUSE_MIDDLE_CLICK 0x04

#define PS2MOUSE_INTELLIMOUSE_ID 0x03

#define MOUSE_EVENT_QUEUE_MAX_SZ  64

/*
mouse data packet: 
data0:  7: y overflow, 6: x overflow, 5: y sign bit, 4: x sign bit,
	    3: alawys 1  , 2: middle btn, 1: right btn , 0: left btn  .
data1:  x 移动值
data2:  y 移动值
data3:  z 移动值 (如果设备支持)
data4:  7: y always 0, 6: always 0, 5: 鼠标第5键, 4: 鼠标第4键,
	    3: 滚轮水平左滚动, 2: 滚轮水平右滚动, 1: 滚轮垂直下滚动, 0: 滚轮垂直上滚动.
        (如果设备支持)
*/
typedef struct HalDeviceExtension
{
    char irq;           /* irq号 */
    NX_InputEventQueue eventQueue;
    /* 按键记录:
     bit 0 置1表示左键已经按下。
     bit 1 置1表示右键已经按下。
     bit 2 置1表示中键已经按下。
    */
    NX_U8 button_record;
    NX_Bool has_wheel;             /* device has wheel ? */
    NX_Bool device_present;        /* does device exist ? */
    NX_U8 data_state;              /* mouse data state, index in data table */
    NX_U8 data[4];                 /* mouse data get from hardware */
    NX_U32 flags;
} HalDeviceExtension;

NX_PRIVATE HalDeviceExtension halMouseExtension;

NX_PRIVATE NX_U8 ps2mouse_read_data();
NX_PRIVATE void ps2mouse_write_data(NX_U8 data);
NX_PRIVATE void ps2mouse_prepare_for_output();
NX_PRIVATE void ps2mouse_prepare_for_input();
NX_PRIVATE void ps2mouse_expect_ack();
NX_PRIVATE void ps2mouse_initialize_device(HalDeviceExtension *devext);
NX_PRIVATE void ps2mouse_check_device_presence(HalDeviceExtension *devext);
NX_PRIVATE void ps2mouse_initialize(HalDeviceExtension *devext);
NX_PRIVATE NX_U8 ps2mouse_wait_then_read(NX_U8 port);
NX_PRIVATE void ps2mouse_wait_then_write(NX_U8 port, NX_U8 data);
NX_PRIVATE NX_Error MouseIrqHandler(NX_IRQ_Number irqno, void *arg);

NX_PRIVATE void parse_data_packet(HalDeviceExtension *devext)
{
    int x = devext->data[1];
    int y = devext->data[2];
    int z = 0;
    if (devext->has_wheel)
    {
        z = (char)devext->data[3];
    }
    NX_Bool x_overflow = devext->data[0] & 0x40;
    NX_Bool y_overflow = devext->data[0] & 0x80;
    NX_Bool x_sign = devext->data[0] & 0x10;
    NX_Bool y_sign = devext->data[0] & 0x20;
    if (x && x_sign)
    {
        x -= 0x100;
    }
    if (y && y_sign)
    {
        y -= 0x100;
    }
    if (x_overflow || y_overflow)
    {
        x = 0;
        y = 0;
    }
    
    NX_InputEvent e;
    /* 水平方向 */
    if (x) {  /* x轴有变化 */
        e.type = NX_EV_REL;
        e.code = NX_REL_X;
        e.value = x;
        
        /* 推送事件 */
        NX_InputEventQueuePut(&devext->eventQueue, &e);
    }
    /* 垂直方向 */
    if (y) {  /* x轴有变化 */
        e.type = NX_EV_REL;
        e.code = NX_REL_Y;
        y = -y; /* y是倒立的 */
        e.value = y; 
        /* 推送事件 */
        NX_InputEventQueuePut(&devext->eventQueue, &e);
    }
    /* 纵深方向 */
    if (z) {  /* z轴有变化 */
        e.type = NX_EV_REL;
        e.code = NX_REL_WHEEL;
        e.value = z;
        
        /* 推送事件 */
        NX_InputEventQueuePut(&devext->eventQueue, &e);
    }
    /* 同步鼠标移动 */
    e.type = NX_EV_SYNC;
    e.code = 0;
    e.value = 0;
    /* 推送事件 */
    NX_InputEventQueuePut(&devext->eventQueue, &e);

    /* 按钮 */
    devext->data[0] &= 0x07; /* 只取低3位 */
    if (devext->data[0] & PS2MOUSE_LEFT_CLICK)
    {   /* left button */
        if (!(devext->button_record & PS2MOUSE_LEFT_CLICK))
        {   /* left button not pressed */
            devext->button_record |= 0x01; /* record */

            /* 鼠标左键按下 */
            e.type = NX_EV_KEY;
            e.code = NX_BTN_MOUSE_LEFT;
            e.value = 1;    /* 1表示按下 */
            /* 推送事件 */
            NX_InputEventQueuePut(&devext->eventQueue, &e);
        }
    }
    else
    {    /* no left button */
        /* 如果上次是按下，这次又没有按下，就是弹起 */
        if (devext->button_record & PS2MOUSE_LEFT_CLICK)
        {
            devext->button_record &= ~PS2MOUSE_LEFT_CLICK; /* clear record */
            
            /* 鼠标左键弹起 */
            e.type = NX_EV_KEY;
            e.code = NX_BTN_MOUSE_LEFT;
            e.value = 0;    /* 0表示弹起 */
            /* 推送事件 */
            NX_InputEventQueuePut(&devext->eventQueue, &e);
        }
    } 
    if (devext->data[0] & PS2MOUSE_RIGHT_CLICK)
    {   /* right button */
        if (!(devext->button_record & PS2MOUSE_RIGHT_CLICK))
        {   /* right button not pressed */
            devext->button_record |= PS2MOUSE_RIGHT_CLICK; /* record */

            /* 鼠标右键按下 */
            e.type = NX_EV_KEY;
            e.code = NX_BTN_MOUSE_RIGHT;
            e.value = 1;    /* 1表示按下 */
            /* 推送事件 */
            NX_InputEventQueuePut(&devext->eventQueue, &e);
        }
    }
    else
    {    /* no right button */
        /* 如果上次是按下，这次又没有按下，就是弹起 */
        if (devext->button_record & PS2MOUSE_RIGHT_CLICK)
        {
            devext->button_record &= ~PS2MOUSE_RIGHT_CLICK; /* clear record */
            
            /* 鼠标左键弹起 */
            e.type = NX_EV_KEY;
            e.code = NX_BTN_MOUSE_RIGHT;
            e.value = 0;    /* 0表示弹起 */
            /* 推送事件 */
            NX_InputEventQueuePut(&devext->eventQueue, &e);
        }
    } 
    if (devext->data[0] & PS2MOUSE_MIDDLE_CLICK)
    {   /* middle button */
        if (!(devext->button_record & PS2MOUSE_MIDDLE_CLICK))
        {   /* middle button not pressed */
            devext->button_record |= PS2MOUSE_MIDDLE_CLICK; /* record */

            /* 鼠标中键按下 */
            e.type = NX_EV_KEY;
            e.code = NX_BTN_MOUSE_MIDDLE;
            e.value = 1;    /* 1表示按下 */
            /* 推送事件 */
            NX_InputEventQueuePut(&devext->eventQueue, &e);
        }
    }
    else
    {    /* no middle button */
        /* 如果上次是按下，这次又没有按下，就是弹起 */
        if (devext->button_record & PS2MOUSE_MIDDLE_CLICK)
        {
            devext->button_record &= ~PS2MOUSE_MIDDLE_CLICK; /* clear record */
            
            /* 鼠标左键弹起 */
            e.type = NX_EV_KEY;
            e.code = NX_BTN_MOUSE_MIDDLE;
            e.value = 0;    /* 0表示弹起 */
            /* 推送事件 */
            NX_InputEventQueuePut(&devext->eventQueue, &e);
        }
    }

    NX_LOG_D("PS2 Mouse: Buttons %x", devext->data[0] & 0x07);
    NX_LOG_D("Mouse: X %d, Y %d, Z %d", x, y, z);
}

NX_PRIVATE void ps2mouse_commit_packet(HalDeviceExtension *devext) {
    devext->data_state = 0;
    NX_LOG_D("PS2Mouse: raw data: %d, %d %s %s",
            devext->data[1],
            devext->data[2],
            (devext->data[0] & 1) ? "Left" : "",
            (devext->data[0] & 2) ? "Right" : "");
    parse_data_packet(devext);
};

NX_PRIVATE void ps2mouse_wait_then_write(NX_U8 port, NX_U8 data)
{
    ps2mouse_prepare_for_output();
    IO_Out8(port, data);
}

NX_PRIVATE NX_U8 ps2mouse_wait_then_read(NX_U8 port)
{
    ps2mouse_prepare_for_input();
    return IO_In8(port);
}

NX_PRIVATE void ps2mouse_initialize(HalDeviceExtension *devext)
{
    // Enable PS aux port
    ps2mouse_wait_then_write(I8042_STATUS, 0xa8);

    ps2mouse_check_device_presence(devext);

    if (devext->device_present)
    {
        ps2mouse_initialize_device(devext);
    }
}

NX_PRIVATE void ps2mouse_check_device_presence(HalDeviceExtension *devext)
{
    ps2mouse_write_data(PS2MOUSE_REQUEST_SINGLE_PACKET);
    NX_U8 maybe_ack = ps2mouse_read_data();
    if (maybe_ack == I8042_ACK)
    {
        devext->device_present = NX_True;
        NX_LOG_I("ps2mouse: Device detected\n");

        // the mouse will send a packet of data, since that's what we asked
        // for. we don't care about the content.
        ps2mouse_read_data();
        ps2mouse_read_data();
        ps2mouse_read_data();
    }
    else
    {
        devext->device_present = NX_False;
        NX_LOG_I("ps2mouse: Device not detected\n");
    }
}

NX_PRIVATE void ps2mouse_initialize_device(HalDeviceExtension *devext)
{
    if (!devext->device_present)
    {
        return;
    }

    // Enable interrupts
    ps2mouse_wait_then_write(I8042_STATUS, 0x20);

    // Enable the PS/2 mouse IRQ (12).
    // NOTE: The keyboard uses IRQ 1 (and is enabled by bit 0 in this register).
    NX_U8 status = ps2mouse_wait_then_read(I8042_BUFFER) | 2;
    ps2mouse_wait_then_write(I8042_STATUS, 0x60);
    ps2mouse_wait_then_write(I8042_BUFFER, status);

    // Set default settings.
    ps2mouse_write_data(PS2MOUSE_SET_DEFAULTS);
    ps2mouse_expect_ack();

    // Enable.
    ps2mouse_write_data(PS2MOUSE_ENABLE_PACKET_STREAMING);
    ps2mouse_expect_ack();
    ps2mouse_write_data(PS2MOUSE_GET_DEVICE_ID);
    ps2mouse_expect_ack();
    NX_U8 device_id = ps2mouse_read_data();

    if (device_id != PS2MOUSE_INTELLIMOUSE_ID)
    {
        // Send magical wheel initiation sequence.
        ps2mouse_write_data(PS2MOUSE_SET_SAMPLE_RATE);
        ps2mouse_expect_ack();
        ps2mouse_write_data(200);
        ps2mouse_expect_ack();
        ps2mouse_write_data(PS2MOUSE_SET_SAMPLE_RATE);
        ps2mouse_expect_ack();
        ps2mouse_write_data(100);
        ps2mouse_expect_ack();
        ps2mouse_write_data(PS2MOUSE_SET_SAMPLE_RATE);
        ps2mouse_expect_ack();
        ps2mouse_write_data(80);
        ps2mouse_expect_ack();

        ps2mouse_write_data(PS2MOUSE_GET_DEVICE_ID);
        ps2mouse_expect_ack();
        device_id = ps2mouse_read_data();
    }

    if (device_id == PS2MOUSE_INTELLIMOUSE_ID)
    {
        devext->has_wheel = NX_True;
        NX_LOG_I("ps2mouse: Mouse wheel enabled!\n");
    }
    else
    {
        NX_LOG_I("ps2mouse: No mouse wheel detected!\n");
    }

    NX_ASSERT(NX_IRQ_Bind(devext->irq, MouseIrqHandler, devext, "ps/2 mouse", 0) == NX_EOK);
    NX_ASSERT(NX_IRQ_Unmask(devext->irq) == NX_EOK);
}

NX_PRIVATE void ps2mouse_expect_ack()
{
    NX_U8 data = ps2mouse_read_data();
    NX_ASSERT(data == I8042_ACK);
}

NX_PRIVATE void ps2mouse_prepare_for_input()
{
    int timeout = 10000;
    for (;timeout > 0; --timeout)
    {
        if (IO_In8(I8042_STATUS) & 1)
        {
            return;
        }
    }
}

NX_PRIVATE void ps2mouse_prepare_for_output()
{
    int timeout = 10000;
    for (;timeout > 0; --timeout)
    {
        if (!(IO_In8(I8042_STATUS) & 2))
        {
            return;
        }
    }
}

NX_PRIVATE void ps2mouse_write_data(NX_U8 data)
{
    ps2mouse_prepare_for_output();
    IO_Out8(I8042_STATUS, 0xd4);
    ps2mouse_prepare_for_output();
    IO_Out8(I8042_BUFFER, data);
}

NX_PRIVATE NX_U8 ps2mouse_read_data()
{
    ps2mouse_prepare_for_input();
    return IO_In8(I8042_BUFFER);
}

NX_PRIVATE NX_Error MouseRead(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    HalDeviceExtension * ext = device->extension;

    NX_InputEvent e;

    if (!len || len != sizeof(NX_InputEvent))
    {
        return NX_EINVAL;
    }

    if (NX_InputEventQueueGet(&ext->eventQueue, &e) != NX_EOK)
    {
        return NX_EAGAIN;
    }

    NX_MemCopy(buf, &e, sizeof(NX_InputEvent));

    if (outLen)
    {
        *outLen = len;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error MousePoll(struct NX_Device *device, NX_PollState * pState)
{
    HalDeviceExtension * ext = device->extension;

    if (NX_InputEventQueueEmpty(&ext->eventQueue))
    {
        return NX_ENORES;
    }
    pState->outmask = NX_POLL_READ;

    return NX_EOK;
}

NX_PRIVATE NX_DriverOps MouseDriverOps = {
    .read       = MouseRead,
    .poll       = MousePoll,
};

NX_PRIVATE NX_Error MouseIrqHandler(NX_IRQ_Number irqno, void *arg)
{
    HalDeviceExtension * devext = (HalDeviceExtension *)arg;

    while (1)
    {
        NX_U8 status = IO_In8(I8042_STATUS);
        if (!(((status & I8042_WHICH_BUFFER) == I8042_MOUSE_BUFFER) && (status & I8042_BUFFER_FULL)))
        {
            return NX_ERROR;
        }

        NX_U8 data = IO_In8(I8042_BUFFER);
        devext->data[devext->data_state] = data;

        switch (devext->data_state) {
        case 0:
            if (!(data & 0x08)) {
                NX_LOG_E("PS2Mouse: Stream out of sync.\n");
                break;
            }
            ++devext->data_state;
            break;
        case 1:
            ++devext->data_state;
            break;
        case 2:
            if (devext->has_wheel) {
                ++devext->data_state;
                break;
            }  
            ps2mouse_commit_packet(devext);
            break;
        case 3:
            NX_ASSERT(devext->has_wheel);
            ps2mouse_commit_packet(devext);
            break;
        }
    }
    return NX_EOK;
}

NX_PRIVATE void MouseDriverInit(void)
{
    NX_Device *device;
    HalDeviceExtension * devext;

    NX_Driver *driver = NX_DriverCreate(DRV_NAME, NX_DEVICE_TYPE_INPUT, 0, &MouseDriverOps);
    if (driver == NX_NULL)
    {
        NX_LOG_E("create driver failed!");
        return;
    }

    if (NX_DriverAttachDevice(driver, DNX_EV_NAME, &device) != NX_EOK)
    {
        NX_LOG_E("attach device %s failed!", DNX_EV_NAME);
        NX_DriverDestroy(driver);
        return;
    }

    devext = &halMouseExtension;
    device->extension = devext;

    devext->irq = IRQ_MOUSE;

    NX_InputEventQueueInit(&devext->eventQueue, MOUSE_EVENT_QUEUE_MAX_SZ);

    devext->button_record = 0;
    devext->flags = 0;
    devext->has_wheel = NX_False;
    devext->device_present = NX_False;
    devext->data_state = 0;
    int i;
    for (i = 0; i < 4; i++)
    {
        devext->data[i] = 0;
    }

    ps2mouse_initialize(devext);

    if (!devext->device_present) {  /* device not exist! */
        NX_ASSERT(NX_IRQ_Mask(devext->irq) == NX_EOK);
        NX_ASSERT(NX_IRQ_Unbind(devext->irq, NX_NULL) == NX_EOK);
        NX_InputEventQueueExit(&devext->eventQueue);

        NX_DriverDetachDevice(driver, DNX_EV_NAME);
        NX_DriverDestroy(driver);
        return;
    }

    if (NX_DriverRegister(driver) != NX_EOK)
    {
        NX_LOG_E("register driver %s failed!", DRV_NAME);
        
        NX_ASSERT(NX_IRQ_Mask(devext->irq) == NX_EOK);
        NX_ASSERT(NX_IRQ_Unbind(devext->irq, NX_NULL) == NX_EOK);
        NX_InputEventQueueExit(&devext->eventQueue);

        NX_DriverDetachDevice(driver, DNX_EV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
}

NX_PRIVATE void MouseDriverExit(void)
{
    NX_DriverCleanup(DRV_NAME);
}

NX_DRV_INIT(MouseDriverInit);
NX_DRV_EXIT(MouseDriverExit);

#endif /* CONFIG_NX_DRIVER_PS2_MOUSE */
